改訂新版 Swift 実践入門
https://images-na.ssl-images-amazon.com/images/I/51WsZJ6wtIL._SX350_BO1,204,203,200_.jpg
読了 : 2020-10-13
4 章
特定状況においては、引数の宣言を省略できる (簡略引数名 (shorthand argument name)) : $0、$1 などになる
Swift では () と Void は同じ型を表すが、戻り値が存在しないことを表すには Void を使う
引数としてのクロージャ
属性の指定 : func 関数名(引数名: @属性名 クロージャの型) { ... }
属性は 2 種類
escaping 属性
渡されたクロージャが関数のスコープ外で保持される可能性があることを示す
この属性の有無によって、コンパイラはキャプチャするかどうかを判断する
autoclosure 属性
引数を暗黙的にクロージャに包むことで遅延評価を実現する
例
func or(_ lhs: Bool, _ rhs: @autoclosure () -> Bool) -> Bool { ... }
みたいに宣言して、or(calc1(), calc2()) と呼び出すと、or の中で rhs のクロージャを実行してはじめて calc2() の呼び出しが行われるようになる
結構暗黙的で怖いな nobuoka.icon
トレイリングクロージャ (trailing closure) : 関数の最後の引数がクロージャの場合に、クロージャを () の外に書くことができる
関数をクロージャとして扱う
関数はクロージャの一種なので、クロージャとして扱える
let 定数名 = 関数名
パラメータで区別する場合 let 定数名 = 関数名(引数名1:引数名2:)
5 章 : 型の構成要素
Swift の型は、クラス、構造体、列挙型の 3 つとして定義可能
本章では、それらの共通の要素 (メソッドやプロパティなど) について説明
プロパティオブザーバ
ストアドプロパティの値の変更を監視し、変更前と変更後に文を実行するもの
レイジーストアドプロパティ
アクセスされるまで初期化を遅延させる
失敗可能イニシャライザ : イニシャライザが失敗する可能性がある場合、init?(...) として宣言
return nil で失敗を表現
メソッドは戻り値によってもオーバーロード可能
まじか! nobuoka.icon
サブスクリプト : コレクションの要素へのアクセス
array[0] みたいなやつ
定義方法 : subscript(引数) -> 戻り値の型 { ... }
複数引数も可能
6 章 : 型の種類
mutating キーワード : 自身の値を変更する値型のメソッドに付与するキーワード
インスタンスが格納されている変数への暗黙的な再代入が行われる
定数に格納された値型のインスタンスに対しては実行できない
Array<Element> 型や Dictionay<Key, Value> 型も構造体 (= 値型)
メンバーワイズイニシャライザ : 構造体に用意されるデフォルトのイニシャライザ
型が持っているストアドプロパティと同じ名前で引数で、プロパティの初期値を受け取るようになっている
クラス
インスタンスメソッドとクラスメソッドはオーバーライド可能。 スタティックメソッドはオーバーライド不可
final キーワードでクラスやメソッドのオーバーライドを禁止できる
スタティックプロパティ / メソッドとクラスプロパティ / メソッドの使い分けむずかしくない? nobuoka.icon
本書では 「その値がサブクラスで変更される可能性があるかどうかで決まる」 って書かれてるけど、本当にそれだけの判断基準でしかないなら、これらは統合して final キーワードをつけるかどうかで対応すれば良いのでは……? と思ってしまった
「スタティックはその型固有でサブクラスには継承されない (A クラスに定義された bar スタティックプロパティを、サブクラスの B では B.bar みたいにはアクセスできない)」 とかならわかりやすいが
Methods associated with a type rather than an instance of a type must be marked with the static declaration modifier for enumerations and structures, or with either the static or class declaration modifier for classes. A class type method marked with the class declaration modifier can be overridden by a subclass implementation; a class type method marked with class final or static can’t be overridden.
確かにサブクラスにオーバーライドを許すかどうかの違いっぽいな
コンビニエンスイニシャライザには convinience キーワードを付ける
Objective-C だとそういう明示的なものがなくてわかりにくかったので、わかりやすくなって良い nobuoka.icon 列挙型
列挙型のケースには、それぞれに対応する値を設定できる : ローバリュー (raw value) という
全てのケースのローバリューの型は同じでなければならない (指定できるのは、Int 型などの、リテラルに変換できる型のみ)
連想値 (associated value) を付与できる (任意の型の値を持てる)。 列挙型のインスタンスごとに異なる値をもてる
7 章 : プロトコル
プロトコルコンポジション : ProtocolA & ProtocolB
連想型
連想型を持つプロトコルは、変数、定数や引数の型として使うことはできず、ジェネリクスの型引数の型制約の記述のみに使用可能
連想型にはデフォルトの型を指定できる
連想型とジェネリクスだとなんか違いがあるのかな nobuoka.icon
クラス専用プロトコル : protocol Foo: class { ... }
参照型のみが準拠できるようにする
プロトコルエクステンションで、プロトコルにデフォルト実装を与えることができる
型制約を追加し、特定の条件を満たす場合にのみプロトコルエクステンションを有効にできる
リテラルから型をインスタンス化するためのプロトコル : ExpressibleByIntegerLiteral など
こういうプロトコルに準拠する型を新たに自分で書いたりできるのかな nobuoka.icon
8 章 : ジェネリクス
型引数には where 節を追加できる。 型引数の連想型について制約を定義できる
9 章 : モジュール
モジュールは配布可能なプログラムの単位
他のプログラムへインポート可能
Swift ではプログラムの名前空間を切る単位でもある
モジュールの作成方法 : 主にはフレームワークとアプリケーション
アクセスレベル
open : すべて許可
public : モジュール外での継承やオーバーライドは不可
あとは internal、fileprivate、private
デフォルトはものによって異なる
型は internal がデフォルト
型全体が internal 以上の公開範囲であれば、要素は internal がデフォルト
さもなければ、要素の公開範囲は型の公開範囲と同じ
構造体のメンバーワイズイニシャライザのデフォルトはストアドプロパティのアクセスレベルによって変わる
型の定義とエクステンションのスコープは本来は別だが、同一ファイル内のエクステンションは同一スコープだとみなされる
型の private な要素にエクステンションからアクセス可能
モジュールのインターフェイス記述を Xcode を通じて閲覧できる
Ctrl + Command を押しながら対象のモジュールをクリック
ドキュメントコメント
tbd
10 章 : 型の設計指針
デイニシャライザはクラスにあって構造体にない → インスタンスのライフサイクルに合わせた処理をしたいときはクラスを利用
11 章 : イベント通知
デリゲート
Cocoa や Cocoa Touch でのデリケートの命名規則
メソッド名はデリゲート元のオブジェクト名から始めて、続けてイベントを説明
did や will でイベントのタイミングを示す
独自でデリゲートを作る場合も同じ命名規則にすると良い
どういうときに使うか
2 つのオブジェクト間で多種類のイベント通知を行うとき
外部からのカスタマイズを前提としたオブジェクトを設計する
クロージャ
キャプチャリストで弱参照を持てる
weak : メモリ解放を想定。 Optional で同名の変数を定義してキャプチャ対象を代入
unowned : メモリ解放を想定しない。 参照先インスタンスが解放されていたら実行時エラー
escaping 属性のクロージャでは、インスタンス自身のプロパティやメソッドへのアクセスに self 必須
キャプチャによる循環参照に気づきやすいように
どういうときに使うか
処理の実行とコールバックを同じ箇所に記述する
実装箇所を見るとコールバック時の処理も把握できるので、デリゲートパターンと比べると処理の流れが追いやすい
オブザーバパターン
通知先が複数の場合
iOS や macOS では、プラットフォームが提供する Notification 型と NotificationCenter クラスを用いて実装できる
Selector 型の生成には #selector キーワードを使用
プロパティの前に setter や getter ラベルを記述してセッタやゲッタのセレクタも取得可能
セレクタは Objective-C の概念なので、Objective-C から参照できる必要がある
利用するべきとき
1 対多のイベント通知を行う
12 章 : 非同期処理
GCD は C 言語ベースの低レベルな API
それを Foundation のクラスとして提供した Operation と OperationQueue もある
GCD
iOS 4.0、Mac OS X 10.6 から導入された
非同期処理を容易にするための C 言語ベースのシステムレベルの技術
ディスパッチキュー = GCD のキュー
2 種類
直列ディスパッチキュー (serial dispatch queue) : 現在実行中の処理を待ってから次の処理へ
並列ディスパッチキュー (concurrent dispatch queue) :
既存のディスパッチキュー
1 つのメインキュー (main queue) : メインスレッドでタスクを実行する直列ディスパッチキュー
DispatchQueue.main
複数のグローバルキュー (global queue) : 実行優先度 (QoS : Quality of Service) により種類がある
userInteractive : アニメーション実行など、ユーザー入力に対してインタラクティブかつ即時に実行されるべきような優先度の高いもの
userInitiated : ユーザー入力を受けて実行されるようなもの
default : 中間の優先度
utility : プログレスバー付きのダウンロードなど、視覚的な情報の更新を伴いながら即時の結果を要求しないもの
background : バックアップなど、目に見えないところで行われて、数分から数時間かかっても問題ない処理
取得は DispatchQueue.global(qos: .userInteractive) のような感じで
DispatchQueue のイニシャライザを使って新規に作成もできる
通常、ラベルには逆順 DNS 形式を用いる
どういうときに使う? → 単純にタスクをほかのスレッドで実行したい場合など
タスクキャンセルなどの複雑な処理をするには向かない (Operation クラスを使うのが良い)
Operation と OperationQueue
OperationQueue の name プロパティには、GCD の場合と同様逆順 DNS を指定するのが一般的
メインスレッドで動く OperationQueue.main はあるが、ほかの汎用的なキューはない
タスクのキャンセル
Operation の cancel() メソッド
これだけではキャンセルされず、Operation のサブクラスでキャンセル時の処理を追加する必要がある
タスクの依存関係 : Operation の addDependency(_:) メソッド
Thread クラス (Thread) : Foundation には Thread クラスもある あるが、Thread を直接使うべきな状況はないはず
「スレッド管理とタスクの管理は別で行うべし」 ってよく言われるもんね nobuoka.icon
非同期処理の結果の受け取り方は、11 章で説明されたイベント通知の手法
13 章 : エラー処理
扱いづらいので、Result<T, Error> 型によるエラー処理がコミュニティで広まった
code:result
enum Result<T, Error : Swift.Error> {
case success(T)
case failure(Error)
}
Swift 2.x : do-catch 文が登場したが、Result<T, Error> によるエラー処理も引き続き使用される
do-catch
code:do-catch
do {
// throw 文によるエラー発生の可能性のある処理
} catch SomeError.caseA(let reason) { // パターンマッチで例外を補足できる
// エラー処理 (パターンマッチを使った場合は error 定数は使えない)
} catch {
// エラー処理 (error 定数を通じてエラー値にアクセス可能)
}
throw 文のエラーを表現する型は Error プロトコルに準拠している必要 (ドキュメント : Error) throws キーワード : エラー発生の可能性のある処理を定義
func 関数名(引数) throws -> 戻り値の型 { ... }
rethrow キーワード : 引数のクロージャが発生させるエラーを呼び出し元に伝搬させる
func 関数名(_ throwingClosure: () throws -> Void) rethrows { ... }
try キーワード : エラーを投げうるメソッドを実行する際に使う必要がある
try! キーワード : エラー処理を記述しないでよい (do-catch を省略できる)
throws が宣言されているが、絶対にエラーが発生しないとわかっている場合に使用
try? キーワード : do-catch を省略できて、結果が Optional<Wrapped> 型になる
defer 文を使うことで、終了処理を記述できる
code:defer
do {
defer {
// do 節を抜けたタイミングで必ず実行される
cleanup()
}
try someFunction()
} catch { ... }
さらに defer の中で例外が発生したら……? とかは気になりますね nobuoka.icon
と思ったけど、エラーが発生する処理は (無視しない限りは) 絶対 do-catch しないといけないから、そもそもコンパイルエラーになったりするのかな
想定外の状況に陥った時のアプリケーション終了の方法として fatalError(_:) によるものとアサーションによるものの 2 つがある
fatalError(_:) : 実行時エラーを発生してプログラムが終了
戻り値は Never 型 (プログラムが終了するので処理が返ってこないということを表す)
アサーション : プログラムがある時点で満たしているべき条件を記述する機能
条件が満たされていない場合にプログラム終了 (ただし、デバッグ時のみ。 リリース時には条件によらず処理が継続される)
デバッグ時のみかどうかというのは、Swift のコンパイル時の最適化レベルで決まる
主な最適化レベル -Onone と -O
-Onone はまったく最適化されない。 アサーション有効
-O では最適化が行われる
assert(_:_:) 関数 : 条件を満たさない場合に終了する
assertionFailure(_:) 関数 : 常に終了する
14 章 : 実践的な Swift アプリケーション
Foundation における HTTP クライアントの実装
URLSession クラス
URLRequest 型の情報をもとに HTTP サーバーと通信し、結果は HTTPURLResponse とバイナリデータを表す Data 型のペアとして表現される
リクエストごとの通信タスクは URLSessionTask
JSON として解釈可能な Data 型は、JSONDecoder を使って独自定義の型に変換できる
15 章 : Swift から Objective-C を利用する
nonobjc 属性 : @objcMember 属性でクラス全体のメンバーを Objective-C から利用可能にしているときに一部のメンバーを Objective-C からは利用できないようにする、とかに使える
dynamic キーワード
@objc 属性などをつけても、必ずしも Objective-C ランタイム経由で実行されるとは限らない (コンパイラはできる限り静的に解決しようとする)
実行時のメソッド置換をしたい場合など、絶対に動的ディスパッチをさせたい場合に dynamic キーワードを使う
Swift から Objective-C のコードを使いやすくするためのトピック
ライトウェイトジェネリクス : Swift 側でも有効
__kindof キーワード : NSArray<__kindof Animal *> *animals みたいな感じで、サブクラスが入ることを表現
null 許容性アノテーション
Swift では、ExpressibleByNilLiteral プロトコルに準拠した型でなければ nil を代入できない
ほー、そうなのか nobuoka.icon
null 許容性アノテーション 4 種類
nonnull
nullable
null_unspecified
null_resettable : プロパティは常に値を持っているが、nil の代入でリセット可能 (Swift からは Optional<Wrapped> 型として見える)
リセット可能とは? nobuoka.icon
適用方法
プロパティ : @property (nonnull) id nonnullValue;
関数 : - (void)sample:(nonnull NSString*)value { ... }
__nonnull みたいな亜種とかがあってよくわかってないんだよな nobuoka.icon
指定の範囲を全部 nonnull にする : NS_ASSUME_NONNULL_BEGIN と NS_ASSUME_NONNULL_END
id 型はできるだけ利用しない
Objective-C はあくまで動的な言語であることに注意
Objective-C のクラスファクトリメソッド
クラス名With... という名前で、戻り値が id 型か instancetype キーワードのクラスメソッド
これは、Swift ではイニシャライザに変換される
型の対応関係
NSInteger → Int
C の構造体はそのまま Swift の構造体に
C の構造体に Swift でエクステンションを定義できる
Objective-C のブロックと Swift のクロージャは相互変換可能な概念
ブロックにおいてキャプチャされた変数をブロック内で再代入可能にするための __block キーワードに相当するものは Swift にはない (Swift では常に再代入可能)